
import mxgraph from 'pages/mxgraph/graph/index';
import Editor, { Dialog } from "src/pages/mxgraph/graph/Editor";
import canvasUtil from './canvasUtil';
import { getStyleObj, getStyleStr } from 'src/util/common';

const {
  mxVertexHandler,
  mxConstants,
  mxGraphHandler,
  mxEvent,
  mxPoint,
  mxUtils,
  mxResources,
  mxRectangle,
  mxRectangleShape,

} = mxgraph;

mxVertexHandler.prototype.reset = function () {

  if (this.sizers !== null && this.index !== null && this.sizers[this.index] !== null &&
    this.sizers[this.index].node.style.display === 'none') {
    this.sizers[this.index].node.style.display = '';
  }

  this.currentAlpha = null;
  this.inTolerance = null;
  this.index = null;

  //// TODO: Reset and redraw cell states for live preview
  if (this.preview !== null && this.preview !== undefined) {
    this.preview.destroy();
    this.preview = null;
  }

  if (this.ghostPreview) {
    this.ghostPreview.destroy();
    this.ghostPreview = null;
  }

  if (this.livePreviewActive && this.sizers != null) {
    for (let i = 0; i < this.sizers.length; i++) {
      if (this.sizers[i] != null) {
        this.sizers[i].node.style.display = '';
      }
    }

    //// Shows folding icon
    if (this.state.control != null && this.state.control.node != null) {
      this.state.control.node.style.visibility = '';
    }
  }

  if (this.customHandles) {
    for (let i = 0; i < this.customHandles.length; i++) {
      if (this.customHandles[i].active) {
        this.customHandles[i].active = false;
        this.customHandles[i].reset();
      } else {
        this.customHandles[i].setVisible(true);
      }
    }
  }

  //// Checks if handler has been destroyed
  if (this.selectionBorder) {
    this.selectionBorder.node.style.display = 'inline';
    this.selectionBounds = this.getSelectionBounds(this.state);
    this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
      this.selectionBounds.width, this.selectionBounds.height);
    this.drawPreview();
  }

  this.removeHint();
  this.redrawHandles();
  this.edgeHandlers = null;
  this.unscaledBounds = null;
  this.livePreviewActive = null;
};

/**
 * Function: mouseUp
 *
 * Handles the event by applying the changes to the geometry.
 */
mxVertexHandler.prototype.mouseUp = function (sender, me) {
  if (this.index != null && this.state != null) {
    let point = new mxPoint(me.getGraphX(), me.getGraphY());
    let index = this.index;
    this.index = null;

    if (this.ghostPreview == null) {
      //// Required to restore order in case of no change
      this.state.view.invalidate(this.state.cell, false, false);
      this.state.view.validate();
    }

    this.graph.getModel().beginUpdate();
    try {
      if (index <= mxEvent.CUSTOM_HANDLE) {
        if (this.customHandles != null) {
          //// Creates style before changing cell state
          let style = this.state.view.graph.getCellStyle(this.state.cell);

          this.customHandles[mxEvent.CUSTOM_HANDLE - index].active = false;
          this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me);

          //// Sets style and apply on shape to force repaint and
          //// check if execute has removed custom handles
          if (this.customHandles != null &&
            this.customHandles[mxEvent.CUSTOM_HANDLE - index] != null) {
            this.state.style = style;
            this.customHandles[mxEvent.CUSTOM_HANDLE - index].positionChanged();
          }
        }
      } else if (index == mxEvent.ROTATION_HANDLE) {
        if (this.currentAlpha != null) {
          let delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0);

          if (delta != 0) {
            this.rotateCell(this.state.cell, delta);
          }
        } else {
          this.rotateClick();
        }
      } else {
        let gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
        let alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
        let cos = Math.cos(-alpha);
        let sin = Math.sin(-alpha);

        let dx = point.x - this.startX;
        let dy = point.y - this.startY;

        //// Rotates vector for mouse gesture
        let tx = cos * dx - sin * dy;
        let ty = sin * dx + cos * dy;

        dx = tx;
        dy = ty;

        let s = this.graph.view.scale;
        let recurse = this.isRecursiveResize(this.state, me);
        let currentCell = this.state.cell;
        let oldgeo = currentCell ? currentCell.geometry : null;
        this.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s),
          index, gridEnabled, this.isConstrainedEvent(me), recurse);

        //// if (currentCell.typeName === 'BehaviorExecutionSpecification'){
        ////   if (oldgeo != null){
        ////     lifelineUtil.adjustTargetBehavior(oldgeo, this.state.cell, this.graph);
        ////   }
        ////   lifelineUtil.reArrangeLifelineBehavior(currentCell, this.graph);
        //// }

      }
    } finally {
      this.graph.getModel().endUpdate();
    }

    me.consume();
    this.reset();
  }

  //// Shows rotation handle only if one vertex is selected
  if (this.rotationShape != null && this.rotationShape.node != null) {
    this.rotationShape.node.style.display =
      this.graph.getSelectionCount() == 1 ? "" : "none";
  }
};
mxVertexHandler.prototype.createSelectionShape = function(bounds){
  let shape = new mxRectangleShape(
    mxRectangle.fromRectangle(bounds),
    null, this.getSelectionColor());
  shape.strokewidth = this.getSelectionStrokeWidth();

  shape.isDashed = this.isSelectionDashed();
  //// 修改strokeOpacity 使diagram（deep==0）的元素看不到选中的边框
  if (this.state.cell.deep === 0){
    shape.strokeOpacity = 0;

  }

  return shape;
};

/* 顶点事件及图形边框事件初始化 */
mxVertexHandler.prototype.init = function () {
  this.graph = this.state.view.graph;
  this.selectionBounds = this.getSelectionBounds(this.state);
  this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
  this.selectionBorder = this.createSelectionShape(this.bounds);
  //// VML dialect required here for event transparency in IE
  this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
  this.selectionBorder.pointerEvents = false;
  this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
  this.selectionBorder.init(this.graph.getView().getOverlayPane());
  mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);

  if (this.graph.isCellMovable(this.state.cell)) {
    this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);
  }
  //// Adds the sizer handles
  if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) {
    let resizable = this.graph.isCellResizable(this.state.cell);
    this.sizers = [];
    if (resizable || (this.graph.isLabelMovable(this.state.cell) &&
    this.state.width >= 2 && this.state.height >= 2)) {
      let i = 0;
      if (resizable) {
        if (!this.singleSizer) {
          this.sizers.push(this.createSizer('nw-resize', i++));
          this.sizers.push(this.createSizer('n-resize', i++));
          this.sizers.push(this.createSizer('ne-resize', i++));
          this.sizers.push(this.createSizer('w-resize', i++));
          this.sizers.push(this.createSizer('e-resize', i++));
          this.sizers.push(this.createSizer('se-resize', i++));
          this.sizers.push(this.createSizer('s-resize', i++));
        }
        this.sizers.push(this.createSizer('sw-resize', i++));
      }
      let geo = this.graph.model.getGeometry(this.state.cell);
      if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&
      this.graph.isLabelMovable(this.state.cell)) {
      //// Marks this as the label handle for getHandleForEvent
        this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE,
          mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR);
        this.sizers.push(this.labelShape);
      }
    } else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&
    this.state.width < 2 && this.state.height < 2 && this.state.cell.umlShape.box.position !== 'edgeLabel') {
      this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,
        mxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR);
      this.sizers.push(this.labelShape);
    }
  }
  //// Adds the rotation handler
  if (this.isRotationHandleVisible()) {
    this.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE,
      mxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR);
    this.sizers.push(this.rotationShape);
  }
  this.customHandles = this.createCustomHandles();
  this.redraw();
  if (this.constrainGroupByChildren) {
    this.updateMinBounds();
  }
};

const vertexHandlerUnion = mxVertexHandler.prototype.union;

//// 大小缩放预览的时候，对元素的位置和大小进行限制，
mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
{
  //// console.log('union', arguments);
  let result = vertexHandlerUnion.apply(this, arguments);

  //// console.log( this.state.style, index);
  //// 固定高度 index获得的有问题，待研究
  //// if (index === 3 || index === 4){
  ////   result.height = this.state.cell.geometry.height;
  //// } else if (index === 1 || index === 6){
  ////   result.width = this.state.cell.geometry.width;

  //// }
  if (this.state.style.resizeWidth === 1 && this.state.style.resizeHeight !== 1){

    result.height = this.state.cell.geometry.height;
    result.y =  this.state.cell.geometry.y;

  //// 固定宽度
  } else if (this.state.style.resizeWidth !== 1 && this.state.style.resizeHeight === 1){
    result.width = this.state.cell.geometry.width;
    result.x =  this.state.cell.geometry.x;

  }
  //// const minHeight =  box.minHeight  - box.paddingTop -  box.paddingBottom;

  //// if (result.width < minWidth){
  ////   result.width = minWidth;
  ////   //// result.x =
  //// }
  //// if (result.height < minHeight){
  ////   result.height = minHeight;
  //// }
  //// console.log(result);

  return result;
};

mxVertexHandler.prototype.redrawHandles = function () {

  let tol = this.tolerance;
  this.horizontalOffset = 0;
  this.verticalOffset = 0;
  let s = this.bounds;
  const styleObj = getStyleObj(this.state.cell.style);

  if (this.customHandles != null) {
    for (let i = 0; i < this.customHandles.length; i++) {
      let temp = this.customHandles[i].shape.node.style.display;
      this.customHandles[i].redraw();
      this.customHandles[i].shape.node.style.display = temp;

      //// Hides custom handles during text editing
      this.customHandles[i].shape.node.style.visibility =
        (this.isCustomHandleVisible(this.customHandles[i])) ?
          '' : 'hidden';
    }
  }

  if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null) {
    if (this.index == null && this.manageSizers && this.sizers.length >= 4) {
      //// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
      let padding = this.getHandlePadding();
      this.horizontalOffset = padding.x;
      this.verticalOffset = padding.y;

      if (this.horizontalOffset !== 0 || this.verticalOffset !== 0) {
        s = new mxRectangle(s.x, s.y, s.width, s.height);
        s.x -= this.horizontalOffset / 2;
        s.width += this.horizontalOffset;
        s.y -= this.verticalOffset / 2;
        s.height += this.verticalOffset;
      }

      if (this.sizers.length >= 8) {

        if (styleObj.resizeHeight === '1' || styleObj.resizeWidth === '1') {
          if (styleObj.resizeHeight === '1') {

            this.sizers[1].node.style.display = '';
            this.sizers[5].node.style.display = '';
          }
          if (styleObj.resizeWidth === '1') {

            this.sizers[3].node.style.display = '';
            this.sizers[7].node.style.display = '';
          }

        } else {
          this.sizers[0].node.style.display = '';
          this.sizers[2].node.style.display = '';
          this.sizers[5].node.style.display = '';
          this.sizers[7].node.style.display = '';

        }

        //// else if ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) ||
        ////   (s.height < 2 * this.sizers[0].bounds.height + 2 * tol)) {
        ////   this.sizers[0].node.style.display = 'none';
        ////   this.sizers[2].node.style.display = 'none';
        ////   this.sizers[5].node.style.display = 'none';
        ////   this.sizers[7].node.style.display = 'none';

        //// }

      }
    }

    let r = s.x + s.width;
    let b = s.y + s.height;

    if (this.singleSizer) {
      this.moveSizerTo(this.sizers[0], r, b);
    } else {
      let cx = s.x + s.width / 2;
      let cy = s.y + s.height / 2;

      if (this.sizers.length >= 8) {
        let crs = [
          'nw-resize',
          'n-resize',
          'ne-resize',
          'e-resize',
          'se-resize',
          's-resize',
          'sw-resize',
          'w-resize'
        ];

        this.sizers.forEach((sizer, index) => {
          sizer && (sizer.node.style.cursor = crs[index]);
        });
        let alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
        let cos = Math.cos(alpha);
        let sin = Math.sin(alpha);

        let ct = new mxPoint(s.getCenterX(), s.getCenterY());
        let pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct);

        //// if (this.state.cell.typeName == 'BehaviorExecutionSpecification') {
        if (styleObj.resizeHeight === '1' || styleObj.resizeWidth === '1') {
          if (styleObj.resizeHeight === '1') {

            this.moveSizerTo(this.sizers[1], cx - 4, pt.y - 9);
            this.sizers[1].node.style.cursor =  'n-resize';

            pt.x = s.x;
            pt.y = b;
            pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

            this.moveSizerTo(this.sizers[5], cx - 4, pt.y);
            this.sizers[5].node.style.cursor =  'n-resize';
          }
          if (styleObj.resizeWidth === '1') {
            this.moveSizerTo(this.sizers[3], s.x - 9, cy - 4);
            this.sizers[3].node.style.cursor = 'e-resize';

            pt.x = r;
            pt.y = s.y;
            pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
            this.moveSizerTo(this.sizers[7], r, cy - 4);
            this.sizers[7].node.style.cursor = 'e-resize';
          }

        } else {
          this.moveSizerTo(this.sizers[0], pt.x - 9, pt.y - 9);
          this.sizers[0].node.style.cursor = 'nw-resize' || 'url("' + M_IMAGE_PATH + '/iconWrapLeftUp.ico"),auto';

          pt.x = r;
          pt.y = s.y;
          pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

          this.moveSizerTo(this.sizers[2], pt.x, pt.y - 9);
          this.sizers[2].node.style.cursor = 'ne-resize' || 'url("' + M_IMAGE_PATH + '/iconWrapRightUp.ico"),auto';

          pt.x = s.x;
          pt.y = b;
          pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

          this.moveSizerTo(this.sizers[5], pt.x - 9, pt.y);
          this.sizers[5].node.style.cursor = 'ne-resize' || 'url("' + M_IMAGE_PATH + '/iconWrapLeftDown.ico"),auto';

          pt.x = r;
          pt.y = b;
          pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

          this.moveSizerTo(this.sizers[7], pt.x, pt.y);
          this.sizers[7].node.style.cursor = 'nw-resize' || 'url("' + M_IMAGE_PATH + '/iconWrapRightDown.ico"),auto';
        }
      } else if (this.state.width >= 2 && this.state.height >= 2) {
        this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
      } else {
        this.moveSizerTo(this.sizers[0], this.state.x, this.state.y);
      }
    }
  }

  if (this.rotationShape != null) {
    let alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0');
    let cos = Math.cos(alpha);
    let sin = Math.sin(alpha);

    let ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
    let pt = mxUtils.getRotatedPoint(this.getRotationHandlePosition(), cos, sin, ct);

    if (this.rotationShape.node != null) {
      this.moveSizerTo(this.rotationShape, pt.x, pt.y);

      //// Hides rotation handle during text editing
      this.rotationShape.node.style.visibility = (this.state.view.graph.isEditing()) ? 'hidden' : '';
    }
  }

  if (this.selectionBorder != null) {
    this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
  }

  if (this.edgeHandlers != null) {
    for (let i = 0; i < this.edgeHandlers.length; i++) {
      this.edgeHandlers[i].redraw();
    }
  }
};

/**
     * Enables recursive resize for groups.
     */
mxVertexHandler.prototype.isRecursiveResize = function (state, me) {
  return (
    !this.graph.isSwimlane(state.cell) &&
        this.graph.model.getChildCount(state.cell) > 0 &&
        !mxEvent.isControlDown(me.getEvent()) &&
        !this.graph.isCellCollapsed(state.cell) &&
        mxUtils.getValue(state.style, "recursiveResize", "1") == "1" &&
        mxUtils.getValue(state.style, "childLayout", null) == null
  );
};

/**
     * Enables centered resize events.
     */
mxVertexHandler.prototype.isCenteredEvent = function (state, me) {
  return (
    (!(
      !this.graph.isSwimlane(state.cell) &&
            this.graph.model.getChildCount(state.cell) > 0 &&
            !this.graph.isCellCollapsed(state.cell) &&
            mxUtils.getValue(state.style, "recursiveResize", "1") == "1" &&
            mxUtils.getValue(state.style, "childLayout", null) == null
    ) &&
          mxEvent.isControlDown(me.getEvent())) ||
        mxEvent.isMetaDown(me.getEvent())
  );
};

let vertexHandlerGetHandlePadding =
      mxVertexHandler.prototype.getHandlePadding;
mxVertexHandler.prototype.getHandlePadding = function () {
  let result = new mxPoint(0, 0);

  return result;
};

/**
     * Updates the hint for the current operation.
     */
mxVertexHandler.prototype.updateHint = function (me) {
  if (this.index != mxEvent.LABEL_HANDLE) {
    if (this.hint == null) {
      this.hint = canvasUtil.createHint();
      this.state.view.graph.container.appendChild(this.hint);
    }

    if (this.index == mxEvent.ROTATION_HANDLE) {
      this.hint.innerHTML = this.currentAlpha + "&deg;";
    } else {
      let s = this.state.view.scale;
      let unit = this.state.view.unit;
      this.hint.innerHTML =
      canvasUtil.formatHintText(this.roundLength(this.bounds.width / s), unit) +
            " x " +
            canvasUtil.formatHintText(this.roundLength(this.bounds.height / s), unit);
    }

    let rot =
          this.currentAlpha != null
            ? this.currentAlpha
            : this.state.style[mxConstants.STYLE_ROTATION] || "0";
    let bb = mxUtils.getBoundingBox(this.bounds, rot);

    if (bb == null) {
      bb = this.bounds;
    }

    this.hint.style.left =
          bb.x + Math.round((bb.width - this.hint.clientWidth) / 2) + "px";
    this.hint.style.top = bb.y + bb.height + 12 + "px";

    if (this.linkHint != null) {
      this.linkHint.style.display = "none";
    }
  }
};

/**
     * Updates the hint for the current operation.
     */
mxVertexHandler.prototype.removeHint = function () {
  this.graph.graphHandler.removeHint.apply(this, arguments);
  //// mxGraphHandler.prototype.removeHint.apply(this, arguments);

  //// if (this.linkHint != null) {
  ////   this.linkHint.style.display = "";
  //// }
};

//// Disables custom handles if shift is pressed
mxVertexHandler.prototype.isCustomHandleEvent = function (me) {
  return !mxEvent.isShiftDown(me.getEvent());
};

//// Uses text bounding box for edge labels
let mxVertexHandlerGetSelectionBounds =
mxVertexHandler.prototype.getSelectionBounds;
mxVertexHandler.prototype.getSelectionBounds = function (state) {
  let model = this.graph.getModel();
  let parent = model.getParent(state.cell);
  let geo = this.graph.getCellGeometry(state.cell);

  if (
    model.isEdge(parent) &&
  geo != null &&
  geo.relative &&
  state.width < 2 &&
  state.height < 2 &&
  state.text != null &&
  state.text.boundingBox != null
  ) {
    let bbox = state.text.unrotatedBoundingBox || state.text.boundingBox;

    return new mxRectangle(
      Math.round(bbox.x),
      Math.round(bbox.y),
      Math.round(bbox.width),
      Math.round(bbox.height)
    );
  } else {
    return mxVertexHandlerGetSelectionBounds.apply(this, arguments);
  }
};

//// Redirects moving of edge labels to mxGraphHandler by not starting here.
//// This will use the move preview of mxGraphHandler (see above).
let mxVertexHandlerMouseDown = mxVertexHandler.prototype.mouseDown;
mxVertexHandler.prototype.mouseDown = function (sender, me) {
  let model = this.graph.getModel();
  let parent = model.getParent(this.state.cell);
  let geo = this.graph.getCellGeometry(this.state.cell);

  //// Lets rotation events through
  let handle = this.getHandleForEvent(me);

  if (
    handle == mxEvent.ROTATION_HANDLE ||
  !model.isEdge(parent) ||
  geo == null ||
  !geo.relative ||
  this.state == null ||
  this.state.width >= 2 ||
  this.state.height >= 2
  ) {
    mxVertexHandlerMouseDown.apply(this, arguments);
  }
};

//// Shows rotation handle for edge labels.
mxVertexHandler.prototype.isRotationHandleVisible = function () {
  return (
    this.graph.isEnabled() &&
  this.rotationEnabled &&
  this.graph.isCellRotatable(this.state.cell) &&
  (mxGraphHandler.prototype.maxCells <= 0 ||
    this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
  );
};

//// Invokes turn on single click on rotation handle
mxVertexHandler.prototype.rotateClick = function () {
  this.state.view.graph.turnShapes([this.state.cell]);
};

let vertexHandlerMouseMove = mxVertexHandler.prototype.mouseMove;

//// Workaround for "isConsumed not defined" in MS Edge is to use arguments
mxVertexHandler.prototype.mouseMove = function (sender, me) {
  vertexHandlerMouseMove.apply(this, arguments);

  if (this.graph.graphHandler.first != null) {
    if (this.rotationShape != null && this.rotationShape.node != null) {
      this.rotationShape.node.style.display = "none";
    }
  }
};

let vertexHandlerMouseUp = mxVertexHandler.prototype.mouseUp;
mxVertexHandler.prototype.mouseUp = function (sender, me) {
  vertexHandlerMouseUp.apply(this, arguments);

  //// Shows rotation handle only if one vertex is selected
  if (this.rotationShape != null && this.rotationShape.node != null) {
    this.rotationShape.node.style.display =
    this.graph.getSelectionCount() == 1 ? "" : "none";
  }
};

let vertexHandlerInit = mxVertexHandler.prototype.init;
mxVertexHandler.prototype.init = function () {
  vertexHandlerInit.apply(this, arguments);
  let redraw = false;

  if (this.rotationShape != null) {
    this.rotationShape.node.setAttribute(
      "title",
      mxResources.get("rotateTooltip")
    );
  }

  let update = mxUtils.bind(this, function () {
  //// Shows rotation handle only if one vertex is selected
    if (this.rotationShape != null && this.rotationShape.node != null) {
      this.rotationShape.node.style.display =
      this.graph.getSelectionCount() == 1 ? "" : "none";
    }

    if (this.specialHandle != null) {
      this.specialHandle.node.style.display =
      this.graph.isEnabled() &&
      this.graph.getSelectionCount() < this.graph.graphHandler.maxCells
        ? ""
        : "none";
    }

    this.redrawHandles();
  });

  this.selectionHandler = mxUtils.bind(this, function (sender, evt) {
    update();
  });

  this.graph
    .getSelectionModel()
    .addListener(mxEvent.CHANGE, this.selectionHandler);

  this.changeHandler = mxUtils.bind(this, function (sender, evt) {
    this.updateLinkHint(
      this.graph.getLinkForCell(this.state.cell),
      this.graph.getLinksForState(this.state)
    );
    update();
  });

  this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);

  //// Repaint needed when editing stops and no change event is fired
  this.editingHandler = mxUtils.bind(this, function (sender, evt) {
    this.redrawHandles();
  });

  this.graph.addListener(mxEvent.EDITING_STOPPED, this.editingHandler);

  let link = this.graph.getLinkForCell(this.state.cell);
  let links = this.graph.getLinksForState(this.state);
  this.updateLinkHint(link, links);

  if (link != null || (links != null && links.length > 0)) {
    redraw = true;
  }

  if (redraw) {
    this.redrawHandles();
  }
};

mxVertexHandler.prototype.updateLinkHint = function (link, links) {
};

let vertexHandlerReset = mxVertexHandler.prototype.reset;
mxVertexHandler.prototype.reset = function () {
  vertexHandlerReset.apply(this, arguments);

  //// Shows rotation handle only if one vertex is selected
  if (this.rotationShape != null && this.rotationShape.node != null) {
    this.rotationShape.node.style.display =
      this.graph.getSelectionCount() == 1 ? "" : "none";
  }
};

let vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
mxVertexHandler.prototype.destroy = function () {
  vertexHandlerDestroy.apply(this, arguments);

  if (this.linkHint != null) {
    this.linkHint.parentNode.removeChild(this.linkHint);
    this.linkHint = null;
  }

  if (this.selectionHandler != null) {
    this.graph.getSelectionModel().removeListener(this.selectionHandler);
    this.selectionHandler = null;
  }

  if (this.changeHandler != null) {
    this.graph.getModel().removeListener(this.changeHandler);
    this.changeHandler = null;
  }

  if (this.editingHandler != null) {
    this.graph.removeListener(this.editingHandler);
    this.editingHandler = null;
  }
};

//// Adds rotation handle and live preview
mxVertexHandler.prototype.rotationEnabled = true;
mxVertexHandler.prototype.manageSizers = true;
mxVertexHandler.prototype.livePreview = true;

//// Increases default rubberband opacity (default is 20)

mxVertexHandler.prototype.parentHighlightEnabled = true;
mxVertexHandler.prototype.rotationHandleVSpacing = -20;
mxVertexHandler.prototype.handleImage = canvasUtil.vertexImg.mainHandle;
mxVertexHandler.prototype.secondaryHandleImage = canvasUtil.vertexImg.secondaryHandle;
